In this article, I’ll walk you through how we can create a Custom Video Player using HTML, CSS, and JavaScript. This project allows you to control video playback with custom buttons for play, pause, rewind, forward, volume, and fullscreen. It also includes a dynamic progress bar and time display that updates in real time as the video plays. By building this project, you’ll learn how to work with HTML5 video events, handle user interactions, and customize the default video controls to create your own modern media player.
🧩 Project Overview
The main idea of this project is simple:
Create a custom video player interface using HTML, CSS, and JavaScript.
Display the current playback time and total duration dynamically.
Allow users to play, pause, rewind, and forward the video using custom buttons.
Include a progress bar that updates in real-time and lets users jump to any part of the video.
Add volume control and fullscreen functionality for a complete viewing experience.
DOMContentLoaded Event
We wrap the entire code inside DOMContentLoaded to ensure the script runs only after the HTML is fully loaded. This prevents errors when trying to access elements that aren’t available yet.
document.addEventListener("DOMContentLoaded", () => {
// all code goes here
});
2. Selecting Elements
Here, we grab all the required elements from our HTML. These references allow us to manipulate the video, buttons, progress bar, and time display dynamically with JavaScript.
const video = document.getElementById("video");
const playPauseBtn = document.getElementById("play-pause");
const rewindBtn = document.getElementById("rewind");
const forwardBtn = document.getElementById("forward");
const volumeSlider = document.getElementById("volume");
const fullscreenBtn = document.getElementById("fullscreen");
const videoContainer = document.querySelector(".video-container");
const progressBar = document.getElementById("progress-bar");
const progressFilled = document.getElementById("progress-filled");
const currentTimeEl = document.getElementById("current-time");
const durationEl = document.getElementById("duration");
3. Formatting Time
This function converts raw seconds into a user-friendly format (mm:ss). For example, 125 seconds becomes 2:05. This makes the time display readable.
function formatTime(seconds) {
const mins = Math.floor(seconds / 60);
const secs = Math.floor(seconds % 60);
return `${mins}:${secs < 10 ? "0" : ""}${secs}`;
}
4. Updating Progress & Time
This function updates both the progress bar width and the current/duration time. It’s called whenever the video plays or loads metadata. The isNaN(total) check ensures the duration shows 0:00 if the video hasn’t loaded yet.
Code:-
function updateProgress() {
const current = video.currentTime;
const total = video.duration || 0;
const progress = (current / total) * 100;
progressFilled.style.width = `${progress}%`;
currentTimeEl.textContent = formatTime(current);
durationEl.textContent = isNaN(total) ? "0:00" : formatTime(total);
}
5. Play / Pause Button
Clicking the button toggles between Play and Pause. We also change the button icon dynamically (▶️ for play, ⏸️ for pause).
Code:-
playPauseBtn.addEventListener("click", () => {
if (video.paused) {
video.play();
playPauseBtn.textContent = "⏸️";
} else {
video.pause();
playPauseBtn.textContent = "▶️";
}
});
6. Rewind & Forward
These buttons allow the user to jump backward or forward by 10 seconds. We use Math.max and Math.min to prevent the time from going below 0 or beyond the total duration.
Code:-
rewindBtn.addEventListener("click", () => {
video.currentTime = Math.max(0, video.currentTime - 10);
});
forwardBtn.addEventListener("click", () => {
video.currentTime = Math.min(video.duration, video.currentTime + 10);
});
7. Volume Control
This lets users adjust the volume using a range slider. The slider value (between 0 and 1) is directly applied to the video’s volume property.
Code:-
volumeSlider.addEventListener("input", () => {
video.volume = volumeSlider.value;
});
8. Fullscreen Mode
The fullscreen button toggles between entering and exiting fullscreen. The optional chaining (?.) ensures compatibility with browsers that might not support these methods.
Code:-
fullscreenBtn.addEventListener("click", () => {
if (!document.fullscreenElement) {
videoContainer.requestFullscreen?.();
} else {
document.exitFullscreen?.();
}
});
9. Progress Bar Seeking
Clicking anywhere on the progress bar jumps the video to that specific point. We calculate the clicked position relative to the bar’s width and convert it into a time value.
Code:-
progressBar.addEventListener("click", (e) => {
const rect = progressBar.getBoundingClientRect();
const clickX = e.clientX - rect.left;
const newTime = (clickX / rect.width) * video.duration;
video.currentTime = newTime;
});
10. Updating Progress with Video Events
timeupdate: Fires while the video is playing, updating the bar and current time.
loadedmetadata: Runs once the video metadata (like duration) is available.
Code:-
video.addEventListener("timeupdate", updateProgress);
video.addEventListener("loadedmetadata", updateProgress);
Full Code:-
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Simple Custom Video Player</title>
<style>
body {
background: #222222;
display: flex;
align-items: center;
justify-content: center;
height: 100vh;
margin: 0;
font-family: Arial, sans-serif;
color: #fff;
}
.video-container {
position: relative;
width: 800px;
max-width: 100%;
background: #000;
border-radius: 8px;
overflow: hidden;
}
video {
width: 100%;
display: block;
}
/* ✅ Progress bar */
.progress-container {
padding: 8px 10px;
}
.progress-bar {
height: 6px;
background: #444;
cursor: pointer;
width: 100%;
border-radius: 3px;
}
.progress-filled {
height: 100%;
background: #ff5252;
width: 0%;
border-radius: 3px;
}
.time-display {
text-align: right;
font-size: 14px;
margin-bottom: 4px;
opacity: 0.8;
}
.controls {
display: flex;
align-items: center;
justify-content: space-around;
padding: 10px;
background: rgba(0, 0, 0, 0.6);
}
button {
background: transparent;
border: none;
color: white;
font-size: 16px;
cursor: pointer;
}
button:hover {
color: #ff5252;
}
input[type="range"] {
width: 100px;
cursor: pointer;
}
</style>
</head>
<body>
<div class="video-container">
<video id="video">
<source src="js.mp4" type="video/mp4" />
Your browser does not support HTML5 video.
</video>
<!-- ✅ Time + Progress -->
<div class="progress-container">
<div class="time-display">
<span id="current-time">0:00</span> / <span id="duration">0:00</span>
</div>
<div class="progress-bar" id="progress-bar">
<div class="progress-filled" id="progress-filled"></div>
</div>
</div>
<div class="controls">
<button id="play-pause">▶️</button>
<button id="rewind">⏪ 10s</button>
<button id="forward">10s ⏩</button>
<input type="range" id="volume" min="0" max="1" step="0.05" value="1" />
<button id="fullscreen">⛶</button>
</div>
</div>
<script>
document.addEventListener("DOMContentLoaded", () => {
const video = document.getElementById("video");
const playPauseBtn = document.getElementById("play-pause");
const rewindBtn = document.getElementById("rewind");
const forwardBtn = document.getElementById("forward");
const volumeSlider = document.getElementById("volume");
const fullscreenBtn = document.getElementById("fullscreen");
const videoContainer = document.querySelector(".video-container");
const progressBar = document.getElementById("progress-bar");
const progressFilled = document.getElementById("progress-filled");
const currentTimeEl = document.getElementById("current-time");
const durationEl = document.getElementById("duration");
// Format time as mm:ss
function formatTime(seconds) {
const mins = Math.floor(seconds / 60);
const secs = Math.floor(seconds % 60);
return `${mins}:${secs < 10 ? "0" : ""}${secs}`;
}
// Update progress bar + time
function updateProgress() {
const current = video.currentTime;
const total = video.duration || 0;
const progress = (current / total) * 100;
progressFilled.style.width = `${progress}%`;
currentTimeEl.textContent = formatTime(current);
durationEl.textContent = isNaN(total) ? "0:00" : formatTime(total);
}
// Play / Pause toggle
playPauseBtn.addEventListener("click", () => {
if (video.paused) {
video.play();
playPauseBtn.textContent = "⏸️";
} else {
video.pause();
playPauseBtn.textContent = "▶️";
}
});
// Rewind 10s
rewindBtn.addEventListener("click", () => {
video.currentTime = Math.max(0, video.currentTime - 10);
});
// Forward 10s
forwardBtn.addEventListener("click", () => {
video.currentTime = Math.min(video.duration, video.currentTime + 10);
});
// Volume Control
volumeSlider.addEventListener("input", () => {
video.volume = volumeSlider.value;
});
// Fullscreen Toggle
fullscreenBtn.addEventListener("click", () => {
if (!document.fullscreenElement) {
videoContainer.requestFullscreen?.();
} else {
document.exitFullscreen?.();
}
});
// Progress Bar Click (Seek)
progressBar.addEventListener("click", (e) => {
const rect = progressBar.getBoundingClientRect();
const clickX = e.clientX - rect.left;
const newTime = (clickX / rect.width) * video.duration;
video.currentTime = newTime;
});
// Update progress while playing
video.addEventListener("timeupdate", updateProgress);
video.addEventListener("loadedmetadata", updateProgress);
// Reset button when video ends
video.addEventListener("ended", () => {
playPauseBtn.textContent = "▶️";
});
});
</script>
</body>
</html>